package com.anlogic.sdk.bsp.view.ui;

import java.awt.Desktop;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.logging.log4j.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.IJobChangeListener;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.events.IHyperlinkListener;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;
import org.eclipse.ui.progress.UIJob;

import com.anlogic.sdk.bsp.model.IPlatformProject;
import com.anlogic.sdk.bsp.settings.BspSettingTool;
import com.anlogic.sdk.bsp.settings.BspSettingsView;
import com.anlogic.sdk.bsp.settings.IBspSettingTool;
import com.anlogic.sdk.bsp.utils.PlatformUtils;
import com.anlogic.sdk.tools.model.DocInfo;
import com.anlogic.sdk.tools.model.DriverInfo;
import com.anlogic.sdk.tools.model.LibraryInfo;
import com.anlogic.sdk.tools.model.OSInfo;
import com.anlogic.sdk.tools.utils.ToolsUtils;
import com.anlogic.sdk.utils.FileUtils;
import com.anlogic.sdk.utils.LogUtils;
import com.anlogic.sdk.utils.ProjectUtils;
import com.anlogic.sdk.utils.UIUtils;
import com.anlogic.sdk.utils.options.ChipType;

public class MSSViewer extends FormPage implements IResourceChangeListener {
	private static Logger logger = LogUtils.getLogger(MSSViewer.class);

	private FormToolkit toolkit;

	private ScrolledForm form;

	private IProject project;

	private IPlatformProject platformProject;

	private IPath mssRelativePath;

	private Label hpfPathLabel;

	private Label targetProcLabel;

	private Section libSection;

	private IBspSettingTool bspSettingTool;

	public MSSViewer(FormEditor editor, String id, String title) {
		super(editor, id, title);
		IFileEditorInput fileEditorInput = (IFileEditorInput) editor.getEditorInput();
		IFile file = fileEditorInput.getFile();
		this.project = (IProject) file.getParent();
		this.platformProject = PlatformUtils.getPlatformProjectFromIProject(this.project);
		this.mssRelativePath = (IPath) new Path(this.project.getName() + "/" + file.getName());
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this, 1);
		this.bspSettingTool = new BspSettingTool(this.platformProject);
	}

	@Override
	protected void createFormContent(IManagedForm managedForm) {
		this.toolkit = managedForm.getToolkit();
		this.form = managedForm.getForm();
		this.form.setText(this.project.getName() + " Board Support Package");
		this.toolkit.decorateFormHeading(this.form.getForm());
		TableWrapLayout layout = new TableWrapLayout();
		this.form.getBody().setLayout((Layout) layout);
		addBSPComposite(createComposite(this.form.getBody()));
		addTargetComposite(createComposite(this.form.getBody()));
		addOSComposite(createComposite(this.form.getBody()));
		addDocComposite(createComposite(this.form.getBody()));
		addDriverComposite(createComposite(this.form.getBody()));
		refreshView();
	}

	private Composite createComposite(Composite parent) {
		Composite c = this.toolkit.createComposite(parent, SWT.WRAP);
		TableWrapLayout l = new TableWrapLayout();
		l.numColumns = 1;
		c.setLayout((Layout) l);
		c.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.FILL));
		return c;
	}

	private Composite createFillLayoutComposite(Composite parent) {
		Composite c = this.toolkit.createComposite(parent, SWT.WRAP);
		FillLayout l = new FillLayout();
		c.setLayout(l);
		c.setLayoutData(new TableWrapData(TableWrapData.FILL, TableWrapData.FILL));
		return c;
	}
	
	private Composite createComposite(Composite parent, int cols) {
		Composite c = this.toolkit.createComposite(parent, SWT.WRAP);
		TableWrapLayout l = new TableWrapLayout();
		l.numColumns = cols;
		c.setLayout((Layout) l);
		c.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.FILL));
		return c;
	}

	private Composite createComposite(Section section, int cols) {
		Composite c = this.toolkit.createComposite((Composite) section);
		TableWrapLayout l = new TableWrapLayout();
		l.numColumns = cols;
		c.setLayout((Layout) l);
		section.setClient((Control) c);
		return c;
	}

	private Section createSection(Composite parent, String title) {
		Section s = this.toolkit.createSection(parent, Section.DESCRIPTION);
		TableWrapData td = new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.FILL);
		toolkit.createCompositeSeparator((Composite) s);
		s.setText(title);
		s.setLayoutData(td);
		return s;
	}

	private Label createLabel(String name, Composite parent, int align, int style) {
		Label l = this.toolkit.createLabel(parent, name, style);
		TableWrapData td = new TableWrapData();
		td.align = align;
		l.setLayoutData(td);
		return l;
	}

	private void createHyperLink(String name, String href, Composite c, int align, int style) {
		if (href.equals("")) {
			createLabel("Not found", c, align, style);
			return;
		}
		Hyperlink l = this.toolkit.createHyperlink(c, name, style);
		TableWrapData td = new TableWrapData();
		td.align = align;
		l.setLayoutData(td);
		l.addHyperlinkListener((IHyperlinkListener) new HyperlinkAdapter() {
			public void linkActivated(HyperlinkEvent e) {
				Desktop desktop = Desktop.getDesktop();
				if (Desktop.isDesktopSupported() && desktop.isSupported(Desktop.Action.BROWSE)) {
					try {
						desktop.browse(new URI(href));
					} catch (IOException | URISyntaxException e1) {
						UIUtils.showMessageDialog("Open Document", "Can not open document!", MessageDialog.ERROR);
					}
				}
			}
		});
	}

	private void createImportLink(String driverName, Composite c, int align, int style) {
		if (driverName.equals("")) {
			createLabel("Not found", c, align, style);
			return;
		}
		Hyperlink l = this.toolkit.createHyperlink(c, "Import Examples", style);
		TableWrapData td = new TableWrapData();
		td.align = align;
		l.setLayoutData(td);
		l.addHyperlinkListener((IHyperlinkListener) new HyperlinkAdapter() {
			public void linkActivated(HyperlinkEvent e) {
				IWorkbenchWindow window = MSSViewer.this.getSite().getWorkbenchWindow();
				DriverExampleDialog dialog = new DriverExampleDialog(window.getShell(), platformProject, driverName);
				dialog.open();
			}
		});
	}

	private void addBSPComposite(Composite parent) {
		Composite c = createComposite(parent, 3);
		Button modifyBspSettingsBtn = this.toolkit.createButton(c, "Modify this BSP's Settings", 0);
		modifyBspSettingsBtn.setLayoutData(new TableWrapData());
		modifyBspSettingsBtn.addSelectionListener((SelectionListener) new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				IWorkbenchWindow window = MSSViewer.this.getSite().getWorkbenchWindow();
				BspSettingsView view = new BspSettingsView(window.getShell(), bspSettingTool);
				view.open();
				refreshView();
			}
		});

		Button modifyHpfBtn = this.toolkit.createButton(c, "Change Referenced HPF", 0);
		modifyHpfBtn.setLayoutData(new TableWrapData());
		modifyHpfBtn.addSelectionListener((SelectionListener) new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				IWorkbenchWindow window = MSSViewer.this.getSite().getWorkbenchWindow();
				FileDialog dialog = new FileDialog(window.getShell(), SWT.OPEN);
				dialog.setFilterExtensions(new String[] { "*.hpf" });
				String result = dialog.open();
				if (result != null && !result.isEmpty()) {
					String bspChip = platformProject.getChip();
					String hpfChip = bspSettingTool.getChipFromHpf(result);
					if (!bspChip.contains(hpfChip)) {
						UIUtils.showMessageDialog("Change Referenced HPF",
								String.format("Platform is based on %s, but the selected hpf supports only %s", bspChip,
										hpfChip),
								MessageDialog.ERROR);
						return;
					}
					logger.info("Starting change referenced hpf");
					bspSettingTool.updateBspHpf(platformProject.getLoc(), result);
					platformProject.restoreMetaData();
					platformProject.refreshLocal(null);
					bspSettingTool.restoreMss();

					if (platformProject.getChip().equals(ChipType.PH1P35.getName())) {
						IProject[] projects = ProjectUtils.getReferencedThisProjects(platformProject.getIProject());
						for (IProject proj : projects) {
							bspSettingTool.updateAppLinkscript(platformProject.getLoc(), proj.getLocation().toString());
						}
					}
					refreshView();
					logger.info("Change referenced hpf done.");
					UIUtils.showMessageDialog("Change Referenced HPF","Change referenced hpf done", MessageDialog.INFORMATION);
				}
			}
		});

		Button regenBspSourcesBtn = this.toolkit.createButton(c, "Re-generate BSP Sources", 0);
		regenBspSourcesBtn.setLayoutData(new TableWrapData());
		regenBspSourcesBtn.addSelectionListener((SelectionListener) new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				logger.info("Starting regenerate bsp sources");
				bspSettingTool.updateBspSource();
				platformProject.refreshLocal(null);
				refreshView();
				logger.info("regenerate bsp sources done.");
				UIUtils.showMessageDialog("Re-generate BSP Sources","Regenerate bsp sources done", MessageDialog.INFORMATION);
			}
		});
	}

	private void addTargetComposite(Composite parent) {
		Section section = createSection(parent, "Target Information");
		section.setDescription("This Board Support Package is compiled to run on the following target.");
		Composite c = createComposite(section, 2);

		String hpfPath = platformProject.getHpfPath();
		String targetProc = platformProject.getProcessor();

		createLabel("Hardware Specification: ", c, TableWrapData.RIGHT, SWT.NONE);
		this.hpfPathLabel = createLabel(hpfPath, c, TableWrapData.LEFT, SWT.NONE);
		createLabel("Target Processor: ", c, TableWrapData.RIGHT, SWT.NONE);
		this.targetProcLabel = createLabel(targetProc, c, TableWrapData.LEFT, SWT.NONE);
	}

	private void addOSComposite(Composite parent) {
		Section section = createSection(parent, "Operating System");
		section.setDescription("Board Support Package OS.");
		Composite c = createComposite(section, 2);

		OSInfo osInfo = bspSettingTool.getBspOsInfo();

		createLabel("Name: ", c, TableWrapData.RIGHT, SWT.NONE);
		createLabel(osInfo.getName(), c, TableWrapData.LEFT, SWT.NONE);
		createLabel("Version: ", c, TableWrapData.RIGHT, SWT.NONE);
		createLabel(osInfo.getVersion(), c, TableWrapData.LEFT, SWT.NONE);
		createLabel("Description: ", c, TableWrapData.RIGHT, SWT.NONE);
		createLabel(osInfo.getDesc(), c, TableWrapData.LEFT, SWT.NONE);
	}

	private void addDocComposite(Composite parent) {
		Section section = createSection(parent, "Document");
		section.setDescription("Board Support Package Document.");
		Composite c = createComposite(section, 1);

		DocInfo docInfo = null;
		for (DocInfo doc : bspSettingTool.getBspDocInfo().values()) {
			docInfo = doc;
			break;
		}

		String docPath = FileUtils.concatFilePath(ToolsUtils.getBspResourcePath(this.platformProject.getChip()),
				docInfo.getPath());

		URL href = FileUtils.filePathToURL(docPath);

		createHyperLink("Documentation", href.toString(), c, TableWrapData.LEFT, SWT.NONE);
	}

	private void addDrvierItem(Composite c, DriverInfo driver) {
		String name = driver.getName();
		String version = driver.getVersion();
		createLabel(name, c, TableWrapData.RIGHT, SWT.NONE);
		createLabel(version, c, TableWrapData.LEFT, SWT.NONE);

		String bspResourcePath = ToolsUtils.getBspResourcePath(this.platformProject.getChip());
		String driverDocPath = bspResourcePath + "/docs/drivers/" + driver.getBareName().toLowerCase() + "/index.html";
		if (FileUtils.checkFileExists(driverDocPath)) {
			URL href = FileUtils.filePathToURL(driverDocPath);
			createHyperLink("Documentation", href.toString(), c, TableWrapData.LEFT, SWT.NONE);
		} else {
			createLabel("", c, TableWrapData.LEFT, SWT.NONE);
		}
		String[] appNames = this.bspSettingTool.getDriverAppNames(name, platformProject.getOs(),
				platformProject.getChip(), platformProject.getProcessor());
		if (appNames.length != 0) {
			createImportLink(name, c, TableWrapData.LEFT, SWT.NONE);
		} else {
			createLabel("", c, TableWrapData.LEFT, SWT.NONE);
		}
	}
	
	private void addDriverComposite(Composite parent) {
		Section section = createSection(parent, "Peripheral Drivers");
		section.setDescription("Drivers present in the Board Support Package.");
		Composite driverComp = createComposite(section, 1);

		Map<String, DriverInfo> driverMap = bspSettingTool.getBspDriverInfo();

		if (driverMap.isEmpty()) {
			createLabel("No drivers in the Board Support Package", driverComp, TableWrapData.LEFT, SWT.NONE);
		} else {
			AtomicInteger jobCount = new AtomicInteger(driverMap.size());
			IJobChangeListener listener = new IJobChangeListener() {
	            @Override
	            public void done(IJobChangeEvent event) {
	                if (jobCount.decrementAndGet() == 0) {
	                	refreshView();
	                }
	            }
	            @Override
	            public void aboutToRun(IJobChangeEvent event) {}
	            @Override
	            public void running(IJobChangeEvent event) {}
	            @Override
	            public void scheduled(IJobChangeEvent event) {}
	            @Override
	            public void sleeping(IJobChangeEvent event) {}
	            @Override
	            public void awake(IJobChangeEvent event) {}
	        };
	        
			for (DriverInfo driver : driverMap.values()) {
				Composite c = createFillLayoutComposite(driverComp);
				UIJob job = new UIJob("Create MSS Driver Composite") {
					@Override
					public IStatus runInUIThread(IProgressMonitor monitor) {
						synchronized (c) {
							addDrvierItem(c, driver);
						}
						monitor.done();
						return Status.OK_STATUS;
					}
				};
				job.addJobChangeListener(listener);
				job.schedule();
			}
		}
	}

	private void addLibComposite() {
		if (this.libSection != null)
			this.libSection.dispose();
		libSection = createSection(this.form.getBody(), "Libraries");
		libSection.setDescription("Libraries present in the Board Support Package.");
		Composite c = createComposite(libSection, 2);

		Map<String, LibraryInfo> libMap = bspSettingTool.getBspLibInfo();

		if (libMap.isEmpty()) {
			createLabel("No libraries in the Board Support Package", c, TableWrapData.LEFT, SWT.NONE);
		} else {
			for (LibraryInfo lib : libMap.values()) {
				createLabel(lib.getName(), c, TableWrapData.RIGHT, SWT.NONE);
				createLabel(lib.getVersion(), c, TableWrapData.LEFT, SWT.NONE);
			}
		}
		this.form.reflow(true);
	}

	private void refreshView() {
		if (!this.form.isDisposed()) {
			this.hpfPathLabel.setText(platformProject.getHpfPath());
			this.targetProcLabel.setText(platformProject.getProcessor());
			addLibComposite();
			this.form.reflow(true);
		}
	}

	@Override
	public void resourceChanged(IResourceChangeEvent event) {
		IResourceDelta delta = event.getDelta().findMember(this.mssRelativePath);
		if (delta == null)
			return;
		IFile f = ((IFileEditorInput) getEditorInput()).getFile();
		if (!f.exists()) {
			ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
			return;
		}
		Display.getDefault().asyncExec(new Runnable() {
			public void run() {
				MSSViewer.this.refreshView();
			}
		});
	}

	@Override
	public void dispose() {
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
		super.dispose();
	}
}
